using Abp;
using Abp.Authorization;
using Abp.Dependency;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.Extensions;
using Microsoft.EntityFrameworkCore;
using Mt.Site.Core.Base.Domain.Entities.ShortId;
using Mt.Site.Core.Base.Reflection;
using Mt.Site.Core.ContentManagement.Entities;
using Mt.Site.Core.ContentManagement.Values;
using Mt.Site.Core.DataLogs;
using Mt.Site.Core.DiyContent.Form;
using Mt.Site.Core.DiyContent.Form.Entities;
using Mt.Site.Core.Navigations;
using Mt.Site.Core.Navigations.Permissions;
using Mt.Site.Core.Navigations.Route;
using Mt.Site.Core.SEO;
using Mt.Site.Core.StaticPages;
using Mt.Site.Core.Storage;
using Mt.Site.Core.Synchronization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

/// <summary>
/// 修改记录操作日志
/// 20200604
/// 增加仓储注入IDataLogsManager、IRepository、IAbpSession
/// CreateAsync、UpdateAsync、SetStatus、DeleteListAysnc、DeleteAysnc增加操作日志记录
/// </summary>
namespace Mt.Site.Core.ContentManagement.Impl
{
    public class ContentManager : MtSiteDomainServiceBase, IContentManager, ITransientDependency
    {
        private readonly IMenuContentHelper _menuContentHelper;
        private readonly IRepository<Content, Guid> _contentRepository;
        private readonly IRepository<ContentBody, long> _contentBodyRepository;
        private readonly IRepository<ContentField, long> _contentFieldRepository;

        private readonly IRouteStore _routeManager;
        private readonly IStaticPageHelper _staticPageHelper;
        private readonly IRepository<Seo, Guid> _seoRepository;
        private readonly IFileObjectManager _fileObjectManager;
        private readonly IMenuStore _menuStore;
        private readonly IDataLogsManager _dataLogsManager;

        internal readonly static string[] _specialFields = new string[] {
            "Id","TenantId","MenuId" ,"Language","AuxiliaryId","RelateId","Key", "Hit",
            "IsDeleted" ,"DeleterUserId"  ,"DeletionTime", "LastModificationTime",  "LastModifierUserId",  "CreationTime" ,"CreatorUserId" ,"AutoSynchronous"
        };

        private readonly IPermissionChecker _permissionsChecker;

        private readonly IShortIdManager _shortIdManager;

        public ContentManager(
            IMenuContentHelper menuContentHelper,
            IRepository<Content, Guid> contentRepository,
            IRepository<ContentBody, long> contentBodyRepository,
            IRepository<ContentField, long> contentFieldRepository,
            IRouteStore routeManager,
            IStaticPageHelper staticPageHelper,
            IRepository<Seo, Guid> seoRepository,
            IFileObjectManager fileObjectManager,
            IMenuStore menustore,
            IDataLogsManager dataLogsManager,
            IPermissionChecker permissionsChecker,
            IShortIdManager shortIdManager)
        {
            _menuContentHelper = menuContentHelper;
            _contentRepository = contentRepository;
            _contentBodyRepository =  contentBodyRepository;
            _contentFieldRepository = contentFieldRepository;

            _routeManager = routeManager;
            _staticPageHelper = staticPageHelper;
            _seoRepository = seoRepository;
            _fileObjectManager = fileObjectManager;
            _menuStore = menustore;
            _dataLogsManager = dataLogsManager;
            _permissionsChecker = permissionsChecker;
            _shortIdManager = shortIdManager;
        }

        #region 获取配置数据

        public async Task<ContentConfig> GetConfig(string customKey, long menuId)
        {
            return await _menuContentHelper.GetConfig(customKey, menuId);
        }

        public async Task<string> GetCustomKey(string customKey, long menuId)
        {
            return await _menuContentHelper.GetCustomKey(customKey, menuId);
        }

        #endregion


        public async Task<Dictionary<string, object>> GetContentForEdit(Guid? id,long menuId, string customKey, Guid? relateId)
        {
            var result = new Dictionary<string, object>();
            customKey = await GetCustomKey(customKey, menuId);
            var config = await GetConfig(customKey, menuId);
            var detailFields = config.Detail
                .Where(ContentFieldsHelper.CheckDataPredicate())
                .ToList();
            //依据配置返回
            if (id == null)
            {
                foreach (var propertie in detailFields)
                {
                    var defaultValue = TypeHelper.GetDefaultValue(propertie.FieldType, propertie.DefaultValue);
                    result.Add(propertie.FieldName.ToCamelCase(), defaultValue);
                }

                result["order"] = await GetMaxOrderNumber(menuId, relateId, customKey);
                result["publishTime"] = DateTime.Now.ToUnixTimeByMilliseconds();
                result["key"] = customKey;
                result["menuId"] = menuId;
                result["relateId"] = relateId;
                result["routeStr"] = "";
                result["autoSynchronous"] = true;
            }
            else
            {
                var content = await _contentRepository.FirstOrDefaultAsync(id.Value);
                if (content != null)
                {
                    var bodyFields = await _contentBodyRepository.GetAllListAsync(c => c.ContentId == content.Id);
                    var fields = await _contentFieldRepository.GetAllListAsync(c => c.ContentId == content.Id);
                    bool hasAdded;

                    foreach (var propertie in detailFields)
                    {
                        hasAdded = false;
                        var propertieKey = propertie.FieldName.ToPascalCase();
                        var value = TypeHelper.GetPropertyValue(content, propertieKey);
                        if (value != null)
                        {
                            hasAdded = true;
                            result.Add(propertieKey.ToCamelCase(), value);
                        }
                        else
                        {
                            ContentBody item = null;
                            if (bodyFields != null)
                            {
                                item = bodyFields.FirstOrDefault(c => c.FieldName == propertieKey);
                                if (item != null)
                                {
                                    hasAdded = true;
                                    result.Add(propertieKey.ToCamelCase(), item.Value);
                                }
                            }
                            if (item == null && fields != null)
                            {
                                var item2 = fields.FirstOrDefault(c => c.FieldName == propertieKey);
                                if (item2 != null)
                                {
                                    hasAdded = true;
                                    result.Add(propertieKey.ToCamelCase(), item2.Value);
                                }
                            }
                        }
                        if (!hasAdded) //正常不会出现
                        {
                            var defaultValue = TypeHelper.GetDefaultValue(propertie.FieldType, propertie.DefaultValue);
                            result[propertieKey.ToCamelCase()] = defaultValue;
                        }
                    }

                    foreach (var propertieKey in _specialFields)
                    {
                        var value = TypeHelper.GetPropertyValue(content, propertieKey);
                        result[propertieKey.ToCamelCase()] = value;
                    }
                    //result["routeStr"] = await _routeManager.GetRouteStrFromDbAndFormatAsync(content.Id, content.TenantId);
                }
            }

            
            return result;
        }

        public async Task<Guid> CreateAsync(Dictionary<string, string> values, long menuId, string customKey, Guid? relateId)
        {
            _permissionsChecker.IsMenuIdPermissionGrantedOrThrowException(menuId, LocalizationSource);

            var content = new Content() { Id = Guid.NewGuid() };

            customKey = await GetCustomKey(customKey, menuId);
            menuId = await CheckMenuId(values, menuId, customKey);

            var config = await GetConfig(customKey, menuId);
            var bodyFields_New = new List<NameValue>();
            var fields_New = new List<NameValue>();
            Mapping(values, content, config, bodyFields_New, fields_New);

            content.MenuId = menuId;
            content.Key = customKey;
            content.RelateId = relateId;
            await _contentRepository.InsertAsync(content);

            foreach (var item in bodyFields_New)
            {
                var body = new ContentBody { ContentId = content.Id, FieldName = item.Name, Value = item.Value };
                await _contentBodyRepository.InsertAsync(body);
            }
            foreach (var item in fields_New)
            {
                var body = new ContentField { ContentId = content.Id, FieldName = item.Name, Value = item.Value };
                await _contentFieldRepository.InsertAsync(body);
            }

            if (MtSiteConsts2.BackgroundJobs)
            {
                _staticPageHelper.DeleteListPage(content.TenantId, content.Language, content.MenuId);
                _staticPageHelper.DeleteStaticPage(content.TenantId, content.Language, 0);
            }


            var optionType = content.IsActive ? DataLogOptionType.CreateAndPublish : DataLogOptionType.Create;
            await _dataLogsManager.CreateAsync(content, optionType);

            await CurrentUnitOfWork.SaveChangesAsync();

            await SetRouteStr(values, content, config, true);

            return content.Id;
        }

        private async Task<long> CheckMenuId(Dictionary<string, string> values, long menuId, string customKey)
        {
            if (values.ContainsKey("menuId"))
            {
                //假设新闻可以切换栏目 (谨慎使用)
                long newMenuId = 0;
                if (long.TryParse(values["menuId"], out newMenuId) && newMenuId != menuId)
                {
                    _permissionsChecker.IsMenuIdPermissionGrantedOrThrowException(newMenuId, LocalizationSource);

                    var newCustomKey = await GetCustomKey(null, newMenuId);
                    if (newCustomKey == customKey)
                    {
                        menuId = newMenuId;
                    }
                }
            }

            return menuId;
        }

        public async Task<Guid> UpdateAsync(Dictionary<string, string> values, Guid contentId, long menuId, string customKey, Guid? relateId)
        {
            _permissionsChecker.IsMenuIdPermissionGrantedOrThrowException(menuId, LocalizationSource);

            var content = await _contentRepository.GetAsync(contentId);
            var bodyFields = await _contentBodyRepository.GetAllListAsync(c => c.ContentId == content.Id);
            var fields = await _contentFieldRepository.GetAllListAsync(c => c.ContentId == content.Id);

            customKey = await GetCustomKey(customKey, menuId);
            menuId = await CheckMenuId(values, menuId, customKey);

            var config = await GetConfig(customKey, menuId);
            var bodyFields_New = new List<NameValue>();
            var fields_New = new List<NameValue>();
            Mapping(values, content, config, bodyFields_New, fields_New);

            content.MenuId = menuId;
            content.Key = customKey;
            content.RelateId = relateId;
            await _contentRepository.UpdateAsync(content);

            #region ContentBody

            // 1 原来没有的，要添加
            foreach (var item in bodyFields_New.Where(p => !bodyFields.Select(c => c.FieldName).Contains(p.Name)))
            {
                var body = new ContentBody { ContentId = content.Id, FieldName = item.Name, Value = item.Value };
                await _contentBodyRepository.InsertAsync(body);
            }
            foreach (var item in bodyFields)
            {
                // 原来有的而现在没有，要删掉; 其他修改
                var bodyField = bodyFields_New.FirstOrDefault(c => c.Name == item.FieldName);
                if (bodyField == null)
                {
                    await _contentBodyRepository.DeleteAsync(item);
                }
                else
                {
                    item.Value = bodyField.Value;
                }
            }
            #endregion

            #region ContentField

            // 1 原来没有的，要添加
            foreach (var item in fields_New.Where(p => !fields.Select(c => c.FieldName).Contains(p.Name)))
            {
                var body = new ContentField { ContentId = content.Id, FieldName = item.Name, Value = item.Value };
                await _contentFieldRepository.InsertAsync(body);
            }
            foreach (var item in fields)
            {
                // 原来有的而现在没有，要删掉; 其他修改
                var bodyField = fields_New.FirstOrDefault(c => c.Name == item.FieldName);
                if (bodyField == null)
                {
                    await _contentFieldRepository.DeleteAsync(item);
                }
                else
                {
                    item.Value = bodyField.Value;
                }
            }
            #endregion

            await SetRouteStr(values, content, config, false);

            if (MtSiteConsts2.BackgroundJobs)
            {
                _staticPageHelper.DeleteListAndDetailPage(content.TenantId, content.Language, content.MenuId, content.AuxiliaryId);
                _staticPageHelper.DeleteStaticPage(content.TenantId, content.Language, 0);
            }

            await _dataLogsManager.CreateAsync(content, DataLogOptionType.Update);

            return content.Id;
        }

        private static void Mapping(Dictionary<string, string> values, Content content, ContentConfig config, List<NameValue> bodyFields_New, List<NameValue> fields_New)
        {
            var checker = new ContentDataChecher();
            Type type = content.GetType();
            var detailFields = config.Detail
                .Where(ContentFieldsHelper.CheckDataPredicate(_specialFields))
                .ToList();
            
            foreach (var item in values)
            {
                if (item.Key != "routeStr")
                {
                    // 按values保存，但要验证合法性
                    var propertie = detailFields.FirstOrDefault(c => c.FieldName.ToCamelCase().Equals(item.Key));
                    if (propertie != null)
                    {
                        var propertieKey = propertie.FieldName.ToPascalCase();
                        var val = checker.Check(propertie.Security, item.Value, propertie.DisplayName, propertie.FieldType);
                        PropertyInfo property = type.GetProperty(propertieKey);
                        if (property != null && property.CanWrite)
                        {
                            TypeHelper.SetPropertyValue(content, propertieKey, val.ConvertTo(property.PropertyType));
                        }
                        else
                        {
                            if (propertie.FieldType == FieldType.Text)
                            {
                                if (val == null)
                                {
                                    bodyFields_New.Add(new NameValue(propertieKey, null));
                                }
                                else
                                {
                                    bodyFields_New.Add(new NameValue(propertieKey, val.ToString()));
                                }
                            }
                            else
                            {
                                if (val == null)
                                {
                                    fields_New.Add(new NameValue(propertieKey, null));
                                }
                                else
                                {
                                    fields_New.Add(new NameValue(propertieKey, val.ToString()));
                                }
                            }
                        }
                    }
                    else
                    {
                        if (item.Key == "order")
                        {
                            int order = 1;
                            int.TryParse(item.Value, out order);
                            content.Order = order;
                        }
                        else if (item.Key == "autoSynchronous")
                        {
                            content.AutoSynchronous = item.Value != "false";
                        }
                    }
                }
            }
        }

        private async Task SetRouteStr(Dictionary<string, string> values, Content content, ContentConfig config, bool isCreating)
        {
            if (config.DiyRoute)
            {
                Guid? frontEndPageId = await _menuStore.GetDetailPageIdAsync(content.MenuId);
                string routeStr = null;
                if (values.ContainsKey("routeStr"))
                {
                    routeStr = values["routeStr"];
                }

                // 20221111修改，为详情页自动生成链接
                if (routeStr.IsNullOrWhiteSpace())
                {
                    var menuItem = await _menuStore.GetMenuIdTraceCacheItemAsync(content.MenuId);
                    Guid menuGuid = menuItem.Id();
                    var listRouteStr = await _routeManager.GetRouteStrFromDbAsync(menuGuid, content.TenantId);
                    if (!listRouteStr.IsNullOrWhiteSpace())
                    {
                        var shortDataId = await _shortIdManager.GetShortIdAsync(content.AuxiliaryId);
                        routeStr = listRouteStr + "/" + shortDataId;
                    }
                }

                if (routeStr.IsNullOrEmpty())
                {
                    if (!isCreating)
                    {
                        await _routeManager.DeleteAsync(content.Id, content.TenantId);
                    }
                }
                else
                {
                    await _routeManager.CreateOrUpdateRouteStrAsync(content.Id, routeStr, frontEndPageId/*可能是关联数据，而不是直接对应栏目*/, content.MenuId, content.AuxiliaryId);
                }
            }
        }

        #region 列表页数据读取

        public async Task<PagedContent<Dictionary<string, object>>> GetContentListByPagedAsync(Func<IQueryable<Content>, IQueryable<Content>> queryMethod, int skipCount, int getCount, string customKey, long menuId, Guid? relateId)
        {
            var query = _contentRepository.GetAll();
            query = queryMethod(query);

            var count = await query.CountAsync();
            var list = await query.Skip(skipCount).Take(getCount).ToListAsync();

            var dir = await TranlateAsConfig(list,  customKey,  menuId);

            var isSingle = await _contentRepository.CountAsync(t => t.MenuId == menuId && t.Key == customKey && t.RelateId == relateId);

            return new PagedContent<Dictionary<string, object>>
            {
                TotolCount = count,
                Items = dir,
                IsSingle = (isSingle < 2)
            };
        }

        private async Task<List<Dictionary<string, object>>> TranlateAsConfig(List<Content> list, string customKey, long menuId)
        {
            var config = await GetConfig(customKey, menuId);
            var result = new List<Dictionary<string, object>>();
            if(config != null)
            {
                var listFields = config.List.Where(c => !string.IsNullOrEmpty(c.FieldName)).Select(c => c.FieldName.ToPascalCase()).ToList();
                foreach (var item in list)
                {
                    var dto = new Dictionary<string, object>();
                    foreach (var propertieKey in listFields)
                    {
                        var value = TypeHelper.GetPropertyValue(item, propertieKey);
                        dto.Add(propertieKey.ToCamelCase(), value);
                    }
                    dto["id"] = item.Id;
                    dto["auxiliaryId"] = item.AuxiliaryId;
                    dto["menuId"] = item.MenuId;
                    dto["relateId"] = item.RelateId;
                    dto["key"] = item.Key;
                    dto["order"] = item.Order;
                    dto["creationTime"] = item.CreationTime.ToUnixTimeByMilliseconds();
                    dto["lastModificationTime"] = item.LastModificationTime == null ? 0 : (item.LastModificationTime.Value.ToUnixTimeByMilliseconds());
                    result.Add(dto);
                }
            }

            return result;
        }
       
        #endregion

        public List<string> GetSpecialFields()
        {
            return _specialFields.Select(c => c.ToCamelCase()).ToList();
        }

        #region --


        public async Task<IdAndMenuId> DeleteAysnc(Guid id)
        {
            var entity = _contentRepository.FirstOrDefault(id);
            if (entity != null && _permissionsChecker.IsGranted(
                MenuPermissions.Pages_Tenant_Menu_Content, entity.MenuId))
            {
                await _routeManager.DeleteAsync(id, entity.TenantId);
                await _seoRepository.DeleteAsync(item => item.RelatedId == id);
                await _fileObjectManager.DeleteListByContentId(id);

                //var config = await GetConfig(entity.Key, entity.MenuId);
                if (MtSiteConsts2.BackgroundJobs)
                {
                    _staticPageHelper.DeleteListAndDetailPage(entity.TenantId, entity.Language, entity.MenuId, entity.AuxiliaryId);
                    _staticPageHelper.DeleteStaticPage(entity.TenantId, entity.Language, 0);
                }

                var result = new IdAndMenuId
                {
                    Id = entity.Id,
                    MenuId = entity.MenuId
                };

                await _contentRepository.DeleteAsync(id);

                await _dataLogsManager.CreateAsync(entity, DataLogOptionType.Delete);

                return result;
            }

            return null;
        }

        public async Task<List<IdAndMenuId>> DeleteListAysnc(IList<Guid> idList)
        {
            var list = await _contentRepository.GetAllListAsync(item => idList.Contains(item.Id));
            //ContentConfig config = null;
            //if(list.Count >0)
            //{
            //    config = await GetConfig(list[0].Key, list[0].MenuId);
            //}

            var result = new List<IdAndMenuId>(); 

            foreach (var item in list)
            {
                if( _permissionsChecker.IsGranted(MenuPermissions.Pages_Tenant_Menu_Content, item.MenuId))
                {
                    var id = item.Id;
                    await _routeManager.DeleteAsync(id, item.TenantId);
                    await _seoRepository.DeleteAsync(c => c.RelatedId == id);
                    await _fileObjectManager.DeleteListByContentId(id);

                    if (MtSiteConsts2.BackgroundJobs)
                    {
                        _staticPageHelper.DeleteListAndDetailPage(item.TenantId, item.Language, item.MenuId, item.AuxiliaryId);
                    }

                    await _contentRepository.DeleteAsync(item);

                    await _dataLogsManager.CreateAsync(item, DataLogOptionType.Delete);

                    result.Add(new IdAndMenuId { Id = item.Id, MenuId = item.MenuId });
                }
            }
            if (MtSiteConsts2.BackgroundJobs)
            {
                _staticPageHelper.DeleteStaticPage(list[0].TenantId, list[0].Language, 0);
            }

            return result;
        }


        private async Task<int> GetMaxOrderNumber(long menuId, Guid? relateId, string key)
        {
            var query = from q in _contentRepository.GetAll()
                        where q.MenuId == menuId && q.RelateId == relateId && q.Key == key
                        orderby q.Order descending
                        select q;
            var m = await query.FirstOrDefaultAsync();
            if (m != null)
            {
                return m.Order + 1;
            }
            else
            {
                return 1;
            }
        }

        public async Task<IdAndMenuId> SetNewOrderNumberAsync(Guid id, int order)
        {
            var entity = await _contentRepository.GetAsync(id);
            entity.Order = order;

            if (MtSiteConsts2.BackgroundJobs)
            {
                _staticPageHelper.DeleteListPage(entity.TenantId, entity.Language, entity.MenuId);
                _staticPageHelper.DeleteStaticPage(entity.TenantId, entity.Language, 0);
            }

            return new IdAndMenuId
            {
                Id = entity.Id,
                MenuId = entity.MenuId
            };
        }

        public async Task<List<IdAndMenuId>> SetStatus(IList<Guid> idList, bool isActive)
        {
            var list = await _contentRepository.GetAllListAsync(c => idList.Contains(c.Id));

            foreach (var item in list)
            {
                if (MtSiteConsts2.BackgroundJobs)
                {
                    if (isActive)
                    {
                        _staticPageHelper.DeleteListPage(item.TenantId, item.Language, item.MenuId);
                    }
                    else
                    {
                        _staticPageHelper.DeleteListAndDetailPage(item.TenantId, item.Language, item.MenuId, item.AuxiliaryId);
                    }
                }
                item.IsActive = isActive;
                
                await _dataLogsManager.CreateAsync(item, item.IsActive ? DataLogOptionType.Publish : DataLogOptionType.UnPublish);
            }

            if (MtSiteConsts2.BackgroundJobs)
            {
                _staticPageHelper.DeleteStaticPage(list[0].TenantId, list[0].Language, 0);
            }

            return list.Select(c => new IdAndMenuId { Id = c.Id, MenuId = c.MenuId }).ToList();
        }

        public async Task<List<IdAndMenuId>> SwapObjectSortAsync(List<Guid> idList, List<int> orderList)
        {
            var list = await _contentRepository.GetAllListAsync(c => idList.Contains(c.Id));

            foreach (var item in list)
            {
                var index = idList.FindIndex(c => c == item.Id);
                if (index != -1)
                {
                    item.Order = orderList[index];
                }

                if (MtSiteConsts2.BackgroundJobs)
                {
                    _staticPageHelper.DeleteListPage(item.TenantId, item.Language, item.MenuId);
                }
            }

            if (MtSiteConsts2.BackgroundJobs)
            {
                _staticPageHelper.DeleteStaticPage(list[0].TenantId, list[0].Language, 0);
            }

            return list.Select(c => new IdAndMenuId { Id = c.Id, MenuId = c.MenuId }).ToList();
        }
        #endregion

        public async Task RecoverContentAsync(List<Guid> idList)
        {
            if (idList.Count > 0)
            {
                using (UnitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete))
                {
                    var contentList = await _contentRepository.GetAllListAsync(item => idList.Contains(item.Id));

                    foreach (var item in contentList)
                    {
                        item.IsDeleted = false;
                        await _contentRepository.UpdateAsync(item);

                        await _dataLogsManager.CreateAsync(item, DataLogOptionType.UnDelete);
                    }
                }
            }
        }
    }
}
